﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using InnovTech.Wpf.Event;
using System.Windows.Input;
using System.Reflection;
using System.Windows.Media;

namespace InnovTech.Wpf.UI.Dragging
{
    public static class DraggingManager
    {

        //public static readonly DependencyProperty ContainerProperty =
        //    DependencyProperty.RegisterAttached("Container", typeof(IInputElement), typeof(DraggingManager));

        //public static void SetContainer(UIElement draggable, IInputElement container)
        //{
        //    draggable.SetValue(ContainerProperty, container);
        //}

        //public static IInputElement GetContainer(UIElement draggable)
        //{
        //    return draggable.GetValue(ContainerProperty) as IInputElement;
        //}


        //public static readonly DependencyProperty IsDraggableProperty =
        //    DependencyProperty.RegisterAttached("IsDraggable", typeof(bool), typeof(DraggingManager));

        //public static void SetIsDraggable(UIElement draggable, bool isDraggable)
        //{
        //    draggable.SetValue(IsDraggableProperty, isDraggable);
        //}

        //public static bool GetIsDraggable(UIElement draggable)
        //{
        //    return (bool)draggable.GetValue(IsDraggableProperty);
        //}

        #region draggableHandleTarget
        public static readonly DependencyProperty DraggableTargetProperty =
            DependencyProperty.RegisterAttached("DraggableTarget", typeof(UIElement), typeof(DraggingManager));

        public static void SetDraggableTarget(UIElement draggable, UIElement target)
        {
            draggable.SetValue(DraggableTargetProperty, target);
        }

        public static UIElement GetDraggableTarget(UIElement draggable)
        {
            return draggable.GetValue(DraggableTargetProperty) as UIElement;
        }
        #endregion draggableHandleTarget

        #region draggable handle

        /// <summary>
        /// indicate whether a draggable child element or his descendant is the target to drag
        /// </summary>
        public static DependencyProperty IsDraggableHandleProperty =
            DependencyProperty.RegisterAttached("IsDraggableHandle", typeof(bool), typeof(DraggingManager), new PropertyMetadata(false, new PropertyChangedCallback(IsDragableHandleChangedCallback)));

        /// <summary>
        /// set a child element of a IMultiLayerContainer or his descendant as the target to drag 
        /// </summary>
        /// <param name="layerObj">a child element or his descendant</param>
        /// <param name="isDragableTarget">true, then the given element is the drag target</param>
        public static void SetIsDraggableHandle(DependencyObject layerObj, bool isDragableTarget)
        {
            layerObj.SetValue(IsDraggableHandleProperty, isDragableTarget);
        }
        /// <summary>
        /// get the info, whether a child element of a IMultiLayerContainer or his descendant is the drag target
        /// </summary>
        /// <param name="layerObj">a child element or his descendant</param>
        /// <returns>true, when the given element is the drag target</returns>
        public static bool GetIsDraggableHandle(DependencyObject layerObj)
        {
            return (bool)layerObj.GetValue(IsDraggableHandleProperty);
        }
        /// <summary>
        /// add the mouse events to a child element in a IMultiLayerContainer or its descendant, when it is marked as the Drag Target
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void IsDragableHandleChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if ((bool)e.NewValue)
            {
                UIElement handle = sender as UIElement;
                handle.MouseLeftButtonDown -= new System.Windows.Input.MouseButtonEventHandler(DraggableHandle_MouseLeftButtonDown);
                handle.MouseMove -= new System.Windows.Input.MouseEventHandler(DraggableHandle_MouseMove);
                handle.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(DraggableHandle_MouseLeftButtonUp);
                handle.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(DraggableHandle_MouseLeftButtonDown);
                handle.MouseMove += new System.Windows.Input.MouseEventHandler(DraggableHandle_MouseMove);
                handle.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(DraggableHandle_MouseLeftButtonUp);
            }
        }
        /// <summary>
        /// when mouse up, dragging finish
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void DraggableHandle_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            UIElement dt = sender as UIElement;
            if (GetIsDragging(dt))
            {
                SetIsDragging(dt, false);
                UIElement draggable = GetDraggableTarget(dt);
                if (draggable != null)
                    draggable = dt;
                EventManagerEx.RaiseRoutedEvent(draggable, DragCompletedEvent);
            }
            dt.ReleaseMouseCapture();
            e.Handled = true;
        }
        /// <summary>
        /// whenn mouse left button is pressed and mouse moved, calculate and set the new location of the child element.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void DraggableHandle_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            UIElement dt = sender as UIElement;

            if (GetIsDragging(dt) && e.LeftButton == MouseButtonState.Pressed)
            {
                UIElement draggable = GetDraggableTarget(dt);
                if (draggable == null) draggable = dt;

                

                //get the mouse position relative to the MultiLayerContainer
                Point mouseToHandle = dt.PointToScreen(e.GetPosition(dt));
                //get the last mouse posistion
                Point last = GetLastLocation(dt);

                double diffX = mouseToHandle.X - last.X;
                double diffY = mouseToHandle.Y - last.Y;

                EventManagerEx.RaiseRoutedEvent(draggable, DraggingEvent);

                Action<UIElement, double, double> dragMethod = GetDragMethod(dt);
                dragMethod(draggable, diffX, diffY);
                //SetLastLocation(dt, dt.PointToScreen(e.GetPosition(dt)));
                SetLastLocation(dt, mouseToHandle);
            }
            e.Handled = true;
        }

        /// <summary>
        /// whenn mouse left button down, the dragging will be started
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void DraggableHandle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (e.ClickCount == 1 && GetIsDraggableHandle(sender as DependencyObject) && sender.Equals(e.OriginalSource))
            {
                UIElement dt = sender as UIElement;
                dt.CaptureMouse();
                UIElement draggable = GetDraggableTarget(dt);
                if (draggable == null)
                    draggable = dt;
                
                SetLastLocation(dt, dt.PointToScreen(e.GetPosition(dt)));
                SetIsDragging(dt, true);
                EventManagerEx.RaiseRoutedEvent(draggable, DragStartEvent);
            }
            e.Handled = true;
        }
        
        #endregion dragable handle

        


        /// <summary>
        /// indicate whether a drag target is being dragged
        /// </summary>
        private static DependencyProperty IsDraggingProperty =
            DependencyProperty.RegisterAttached("IsDragging", typeof(bool), typeof(DraggingManager), new PropertyMetadata(false));

        private static void SetIsDragging(DependencyObject dragTarget, bool isDragging)
        {
            dragTarget.SetValue(IsDraggingProperty, isDragging);
        }

        private static bool GetIsDragging(DependencyObject dragTarget)
        {
            return (bool)dragTarget.GetValue(IsDraggingProperty);
        }

        public static readonly DependencyProperty DragMethodProperty =
            DependencyProperty.RegisterAttached("DragMethod", typeof(Action<UIElement, double, double>), typeof(DraggingManager));

        public static void SetDragMethod(UIElement draggable, Action<UIElement, double, double> dragMethod)
        {
            draggable.SetValue(DragMethodProperty, dragMethod);
        }

        public static Action<UIElement, double, double> GetDragMethod(UIElement draggable)
        {
            return draggable.GetValue(DragMethodProperty) as Action<UIElement, double, double>;
        }

        /// <summary>
        /// the location of a draggable child element before the next moving
        /// </summary>
        private static DependencyProperty LastLocationProperty =
            DependencyProperty.RegisterAttached("LastLocation", typeof(Point), typeof(DraggingManager));

        private static void SetLastLocation(DependencyObject layerObj, Point point)
        {
            layerObj.SetValue(LastLocationProperty, point);
        }
        private static Point GetLastLocation(DependencyObject layerObj)
        {
            return (Point)layerObj.GetValue(LastLocationProperty);
        }

        public static readonly RoutedEvent DraggingEvent =
            EventManager.RegisterRoutedEvent("Dragging", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(DraggingManager));

        public static readonly RoutedEvent DragStartEvent =
            EventManager.RegisterRoutedEvent("DragStart", RoutingStrategy.Direct, typeof(RoutedEvent), typeof(DraggingManager));

        public static readonly RoutedEvent DragCompletedEvent =
            EventManager.RegisterRoutedEvent("DragCompleted", RoutingStrategy.Direct, typeof(RoutedEvent), typeof(DraggingManager));
    }
}
